
#include "w25qxx.h"

#define DUMMY   (0x00)

static void w25qxx_write_enable(w25qxx_t *ic)
{
    ic->bus->enable(ic->bus_handle, 1);
    ic->bus->rw_byte(ic->bus_handle, kW25qxxIns_WriteEnable);
    ic->bus->enable(ic->bus_handle, 0);
}

static void w25qxx_write_disable(w25qxx_t *ic)
{
    ic->bus->enable(ic->bus_handle, 1);
    ic->bus->rw_byte(ic->bus_handle, kW25qxxIns_WriteDisable);
    ic->bus->enable(ic->bus_handle, 0);
}

static void w25qxx_wait_wp(w25qxx_t *ic)
{
    w25qxx_reg_sr_t reg_sr;
    
    ic->bus->enable(ic->bus_handle, 1);
    ic->bus->rw_byte(ic->bus_handle, kW25qxxIns_ReadStatus);
    do {
        reg_sr.value = ic->bus->rw_byte(ic->bus_handle, DUMMY);
    }while(reg_sr.wip==1);
    ic->bus->enable(ic->bus_handle, 0);
}

static void w25qxx_read_id(w25qxx_t *ic)
{
    ic->bus->enable(ic->bus_handle, 1);
    ic->bus->rw_byte(ic->bus_handle, kW25qxxIns_ReadJEDECID);
    ic->id.manufacturer = ic->bus->rw_byte(ic->bus_handle, DUMMY);
    ic->id.memory_type = ic->bus->rw_byte(ic->bus_handle, DUMMY);
    ic->id.capacity = ic->bus->rw_byte(ic->bus_handle, DUMMY);
    ic->bus->enable(ic->bus_handle, 0);
}

static void w25qxx_set_address(w25qxx_t *ic, uint32_t addr)
{
    ic->bus->rw_byte(ic->bus_handle, (addr>>16)&0xff);
    ic->bus->rw_byte(ic->bus_handle, (addr>>8)&0xff);
    ic->bus->rw_byte(ic->bus_handle, addr&0xff);
}

static hal_status_t w25qxx_write_page(w25qxx_t *ic, uint32_t addr, uint8_t *buff, uint16_t len)
{
    uint16_t i;
    w25qxx_write_enable(ic);
    ic->bus->enable(ic->bus_handle, 1);
    ic->bus->rw_byte(ic->bus_handle, kW25qxxIns_WritePage);
    w25qxx_set_address(ic, addr);
    for(i=0;i!=len;i++)
        ic->bus->rw_byte(ic->bus_handle, buff[i]);
    ic->bus->enable(ic->bus_handle, 0);
    w25qxx_wait_wp(ic);
    return kHalStatus_Success;
}

hal_status_t w25qxx_write(w25qxx_t *ic, uint32_t addr, uint8_t *buff, size_t len)
{
    hal_status_t status = kHalStatus_Success;
	size_t len_useful, len_write, len_has_write;
    if(addr+len>(1ul<<ic->size.memory))
        return kHalStatus_InvalidArgument;
    len_useful = W25QXX_PAGE_SIZE - (addr%W25QXX_PAGE_SIZE); //The remaining useful length on the current page
    /* Calculate the length of data that needs to be written on the current page */
    len_write = len<len_useful ? len : len_useful;
    w25qxx_write_page(ic, addr, buff, len_write);
    len -= len_write;
    len_has_write = len_write;
    /* Write data of remaining length */
	while(len>0)
	{
		if(len>W25QXX_PAGE_SIZE)
		{
			w25qxx_write_page(ic, addr + len_has_write, buff + len_has_write, W25QXX_PAGE_SIZE);
			len -= W25QXX_PAGE_SIZE;
			len_has_write += W25QXX_PAGE_SIZE;
		}
		else
		{
			w25qxx_write_page(ic, addr + len_has_write, buff + len_has_write, len);
			len = 0;
		}
	}
	return status;
}

void w25qxx_read(w25qxx_t *ic, uint32_t addr, uint8_t *buff, uint16_t len)
{
    uint16_t i;
    w25qxx_write_enable(ic);
    ic->bus->enable(ic->bus_handle, 1);
    ic->bus->rw_byte(ic->bus_handle, kW25qxxIns_ReadData);
    w25qxx_set_address(ic, addr);
    for(i=0;i!=len;i++)
        buff[i] = ic->bus->rw_byte(ic->bus_handle, DUMMY);
    ic->bus->enable(ic->bus_handle, 0);
}

hal_status_t w25qxx_erase_sector(w25qxx_t *ic, uint32_t addr)
{
    w25qxx_write_enable(ic);
    ic->bus->enable(ic->bus_handle, 1);
    ic->bus->rw_byte(ic->bus_handle, kW25qxxIns_EraseSector);
    w25qxx_set_address(ic, addr);
    ic->bus->enable(ic->bus_handle, 0);
    w25qxx_wait_wp(ic);
    return kHalStatus_Success;
}

void w25qxx_init(w25qxx_t *ic)
{
    w25qxx_read_id(ic);
    ic->size.memory = ic->id.capacity;
    ic->size.sector = 12; //4096
    ic->size.block = 16;
}



